/*
 * TextAreaPainter.java - Paints the text area
 * Copyright (C) 1999 Slava Pestov
 *
 * You may use and modify this package for any purpose. Redistribution is
 * permitted, in both source and binary form, provided that this notice
 * remains intact in all source distributions of this package.
 */
package ui.jedit;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.ToolTipManager;
import javax.swing.text.PlainDocument;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;

import ui.pfont.PFont;

/**
 * The text area repaint manager. It performs double buffering and paints lines
 * of text.
 * 
 * @author Slava Pestov
 * @version $Id: TextAreaPainter.java,v 1.24 1999/12/13 03:40:30 sp Exp $
 */
public class TextAreaPainter extends JComponent implements TabExpander {
    /**
     * Creates a new repaint manager. This should be not be called directly.
     */
    public TextAreaPainter(JEditTextArea textArea, TextAreaDefaults defaults) {
        this.textArea = textArea;

        setAutoscrolls(true);
        setDoubleBuffered(true);
        setOpaque(true);

        ToolTipManager.sharedInstance().registerComponent(this);

        currentLine = new Segment();
        currentLineIndex = -1;

        setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));

        setFont(new Font("Monospaced", Font.PLAIN, 14));
        setForeground(Color.black);
        setBackground(Color.white);

        blockCaret = defaults.blockCaret;
        styles = defaults.styles;
        cols = defaults.cols;
        rows = defaults.rows;
        caretColor = defaults.caretColor;
        selectionColor = defaults.selectionColor;
        lineHighlightColor = defaults.lineHighlightColor;
        lineHighlight = defaults.lineHighlight;
        bracketHighlightColor = defaults.bracketHighlightColor;
        bracketHighlight = defaults.bracketHighlight;
        paintInvalid = defaults.paintInvalid;
        eolMarkerColor = defaults.eolMarkerColor;
        eolMarkers = defaults.eolMarkers;
    }

    /**
     * Returns if this component can be traversed by pressing the Tab key. This
     * returns false.
     */
    public final boolean isManagingFocus() {
        return false;
    }

    /**
     * Returns the syntax styles used to paint colorized text. Entry <i>n</i>
     * will be used to paint tokens with id = <i>n</i>.
     * 
     * @see org.gjt.sp.jedit.syntax.Token
     */
    public final SyntaxStyle[] getStyles() {
        return styles;
    }

    /**
     * Sets the syntax styles used to paint colorized text. Entry <i>n</i> will
     * be used to paint tokens with id = <i>n</i>.
     * 
     * @param styles
     *            The syntax styles
     * @see org.gjt.sp.jedit.syntax.Token
     */
    public final void setStyles(SyntaxStyle[] styles) {
        this.styles = styles;
        repaint();
    }

    /**
     * Returns the caret color.
     */
    public final Color getCaretColor() {
        return caretColor;
    }

    /**
     * Sets the caret color.
     * 
     * @param caretColor
     *            The caret color
     */
    public final void setCaretColor(Color caretColor) {
        this.caretColor = caretColor;
        invalidateSelectedLines();
    }

    /**
     * Returns the selection color.
     */
    public final Color getSelectionColor() {
        return selectionColor;
    }

    /**
     * Sets the selection color.
     * 
     * @param selectionColor
     *            The selection color
     */
    public final void setSelectionColor(Color selectionColor) {
        this.selectionColor = selectionColor;
        invalidateSelectedLines();
    }

    /**
     * Returns the line highlight color.
     */
    public final Color getLineHighlightColor() {
        return lineHighlightColor;
    }

    /**
     * Sets the line highlight color.
     * 
     * @param lineHighlightColor
     *            The line highlight color
     */
    public final void setLineHighlightColor(Color lineHighlightColor) {
        this.lineHighlightColor = lineHighlightColor;
        invalidateSelectedLines();
    }

    /**
     * Returns true if line highlight is enabled, false otherwise.
     */
    public final boolean isLineHighlightEnabled() {
        return lineHighlight;
    }

    /**
     * Enables or disables current line highlighting.
     * 
     * @param lineHighlight
     *            True if current line highlight should be enabled, false
     *            otherwise
     */
    public final void setLineHighlightEnabled(boolean lineHighlight) {
        this.lineHighlight = lineHighlight;
        invalidateSelectedLines();
    }

    /**
     * Returns the bracket highlight color.
     */
    public final Color getBracketHighlightColor() {
        return bracketHighlightColor;
    }

    /**
     * Sets the bracket highlight color.
     * 
     * @param bracketHighlightColor
     *            The bracket highlight color
     */
    public final void setBracketHighlightColor(Color bracketHighlightColor) {
        this.bracketHighlightColor = bracketHighlightColor;
        invalidateLine(textArea.getBracketLine());
    }

    /**
     * Returns true if bracket highlighting is enabled, false otherwise. When
     * bracket highlighting is enabled, the bracket matching the one before the
     * caret (if any) is highlighted.
     */
    public final boolean isBracketHighlightEnabled() {
        return bracketHighlight;
    }

    /**
     * Enables or disables bracket highlighting. When bracket highlighting is
     * enabled, the bracket matching the one before the caret (if any) is
     * highlighted.
     * 
     * @param bracketHighlight
     *            True if bracket highlighting should be enabled, false
     *            otherwise
     */
    public final void setBracketHighlightEnabled(boolean bracketHighlight) {
        this.bracketHighlight = bracketHighlight;
        invalidateLine(textArea.getBracketLine());
    }

    /**
     * Returns true if the caret should be drawn as a block, false otherwise.
     */
    public final boolean isBlockCaretEnabled() {
        return blockCaret;
    }

    /**
     * Sets if the caret should be drawn as a block, false otherwise.
     * 
     * @param blockCaret
     *            True if the caret should be drawn as a block, false otherwise.
     */
    public final void setBlockCaretEnabled(boolean blockCaret) {
        this.blockCaret = blockCaret;
        invalidateSelectedLines();
    }

    /**
     * Returns the EOL marker color.
     */
    public final Color getEOLMarkerColor() {
        return eolMarkerColor;
    }

    /**
     * Sets the EOL marker color.
     * 
     * @param eolMarkerColor
     *            The EOL marker color
     */
    public final void setEOLMarkerColor(Color eolMarkerColor) {
        this.eolMarkerColor = eolMarkerColor;
        repaint();
    }

    /**
     * Returns true if EOL markers are drawn, false otherwise.
     */
    public final boolean getEOLMarkersPainted() {
        return eolMarkers;
    }

    /**
     * Sets if EOL markers are to be drawn.
     * 
     * @param eolMarkers
     *            True if EOL markers should be drawn, false otherwise
     */
    public final void setEOLMarkersPainted(boolean eolMarkers) {
        this.eolMarkers = eolMarkers;
        repaint();
    }

    /**
     * Returns true if invalid lines are painted as red tildes (~), false
     * otherwise.
     */
    public boolean getInvalidLinesPainted() {
        return paintInvalid;
    }

    /**
     * Sets if invalid lines are to be painted as red tildes.
     * 
     * @param paintInvalid
     *            True if invalid lines should be drawn, false otherwise
     */
    public void setInvalidLinesPainted(boolean paintInvalid) {
        this.paintInvalid = paintInvalid;
    }

    /**
     * Adds a custom highlight painter.
     * 
     * @param highlight
     *            The highlight
     */
    public void addCustomHighlight(Highlight highlight) {
        highlight.init(textArea, highlights);
        highlights = highlight;
    }

    /**
     * Highlight interface.
     */
    public interface Highlight {
        /**
         * Called after the highlight painter has been added.
         * 
         * @param textArea
         *            The text area
         * @param next
         *            The painter this one should delegate to
         */
        void init(JEditTextArea textArea, Highlight next);

        /**
         * This should paint the highlight and delgate to the next highlight
         * painter.
         * 
         * @param gfx
         *            The graphics context
         * @param line
         *            The line number
         * @param y
         *            The y co-ordinate of the line
         */
        void paintHighlight(Graphics gfx, int line, int y);

        /**
         * Returns the tool tip to display at the specified location. If this
         * highlighter doesn't know what to display, it should delegate to the
         * next highlight painter.
         * 
         * @param evt
         *            The mouse event
         */
        String getToolTipText(MouseEvent evt);
    }

    /**
     * Returns the tool tip to display at the specified location.
     * 
     * @param evt
     *            The mouse event
     */
    public String getToolTipText(MouseEvent evt) {
        if (highlights != null)
            return highlights.getToolTipText(evt);
        else
            return null;
    }

    /**
     * Returns the font metrics used by this component.
     */
    public FontMetrics getFontMetrics() {
        return fm;
    }

    /**
     * Sets the font for this component. This is overridden to update the cached
     * font metrics and to recalculate which lines are visible.
     * 
     * @param font
     *            The font
     */
    public void setFont(Font font) {
        super.setFont(font);
        fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
        textArea.recalculateVisibleLines();
    }

    /**
     * Repaints the text.
     * 
     * @param g
     *            The graphics context
     */
    public void paint(Graphics gfx) {
        tabSize = fm.charWidth(' ')
                * ((Integer) textArea.getDocument().getProperty(
                        PlainDocument.tabSizeAttribute)).intValue();

        Rectangle clipRect = gfx.getClipBounds();

        gfx.setColor(getBackground());
        gfx.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

        // We don't use yToLine() here because that method doesn't
        // return lines past the end of the document
        int height = fm.getHeight();
        int firstLine = textArea.getFirstLine();
        int firstInvalid = firstLine + clipRect.y / height;
        // Because the clipRect's height is usually an even multiple
        // of the font height, we subtract 1 from it, otherwise one
        // too many lines will always be painted.
        int lastInvalid = firstLine + (clipRect.y + clipRect.height - 1)
                / height;

        try {
            TokenMarker tokenMarker = textArea.getDocument().getTokenMarker();
            int x = textArea.getHorizontalOffset();

            for (int line = firstInvalid; line <= lastInvalid; line++) {
                paintLine(gfx, tokenMarker, line, x);
            }

            if (tokenMarker != null && tokenMarker.isNextLineRequested()) {
                int h = clipRect.y + clipRect.height;
                repaint(0, h, getWidth(), getHeight() - h);
            }
        } catch (Exception e) {
            System.err.println("Error repainting line" + " range {"
                    + firstInvalid + "," + lastInvalid + "}:");
            e.printStackTrace();
        }
    }

    /**
     * Marks a line as needing a repaint.
     * 
     * @param line
     *            The line to invalidate
     */
    public final void invalidateLine(int line) {
        repaint(0, textArea.lineToY(line) + fm.getMaxDescent()
                + fm.getLeading(), getWidth(), fm.getHeight());
    }

    /**
     * Marks a range of lines as needing a repaint.
     * 
     * @param firstLine
     *            The first line to invalidate
     * @param lastLine
     *            The last line to invalidate
     */
    public final void invalidateLineRange(int firstLine, int lastLine) {
        repaint(0, textArea.lineToY(firstLine) + fm.getMaxDescent()
                + fm.getLeading(), getWidth(), (lastLine - firstLine + 1)
                * fm.getHeight());
    }

    /**
     * Repaints the lines containing the selection.
     */
    public final void invalidateSelectedLines() {
        invalidateLineRange(textArea.getSelectionStartLine(), textArea
                .getSelectionEndLine());
    }

    /**
     * Implementation of TabExpander interface. Returns next tab stop after a
     * specified point.
     * 
     * @param x
     *            The x co-ordinate
     * @param tabOffset
     *            Ignored
     * @return The next tab stop after <i>x</i>
     */
    public float nextTabStop(float x, int tabOffset) {
        int offset = textArea.getHorizontalOffset();
        int ntabs = ((int) x - offset) / tabSize;
        return (ntabs + 1) * tabSize + offset;
    }

    /**
     * Returns the painter's preferred size.
     */
    public Dimension getPreferredSize() {
        Dimension dim = new Dimension();
        dim.width = fm.charWidth('w') * cols;
        dim.height = fm.getHeight() * rows;
        return dim;
    }

    /**
     * Returns the painter's minimum size.
     */
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    // package-private members
    int currentLineIndex;
    Token currentLineTokens;
    Segment currentLine;

    // protected members
    protected JEditTextArea textArea;

    protected SyntaxStyle[] styles;
    protected Color caretColor;
    protected Color selectionColor;
    protected Color lineHighlightColor;
    protected Color bracketHighlightColor;
    protected Color eolMarkerColor;

    protected boolean blockCaret;
    protected boolean lineHighlight;
    protected boolean bracketHighlight;
    protected boolean paintInvalid;
    protected boolean eolMarkers;
    protected int cols;
    protected int rows;

    protected int tabSize;
    protected FontMetrics fm;

    protected Highlight highlights;

    protected void paintLine(Graphics gfx, TokenMarker tokenMarker, int line,
            int x) {
        Font defaultFont = getFont();
        Color defaultColor = getForeground();

        currentLineIndex = line;
        int y = textArea.lineToY(line);

        if (line < 0 || line >= textArea.getLineCount()) {

        } else if (tokenMarker == null) {
            System.out.println("TOKENMARKER NULL");
            paintPlainLine(gfx, line, defaultFont, defaultColor, x, y);
        } else {
            paintSyntaxLine(gfx, tokenMarker, line, defaultFont, defaultColor,
                    x, y);
        }
    }

    protected void paintPlainLine(Graphics gfx, int line, Font defaultFont,
            Color defaultColor, int x, int y) {
        if (textArea.pcffont == null) {
            paintHighlight(gfx, line, y);
            textArea.getLineText(line, currentLine);

            gfx.setFont(defaultFont);
            gfx.setColor(defaultColor);

            y += fm.getHeight();
            x = Utilities.drawTabbedText(currentLine, x, y, gfx, this, 0);

        } else {
            paintHighlight(gfx, line, y);
            textArea.getLineText(line, currentLine);

            textArea.pcffont.setFgColor(this.getForeground());

            y += textArea.pcffont.getHeight();
            x = drawTabbedPCFText(textArea.pcffont, currentLine, x, y, gfx,
                    this);
        }

    }

    protected void paintSyntaxLine(Graphics gfx, TokenMarker tokenMarker,
            int line, Font defaultFont, Color defaultColor, int x, int y) {
        textArea.getLineText(currentLineIndex, currentLine);
        currentLineTokens = tokenMarker.markTokens(currentLine,
                currentLineIndex);

        paintHighlight(gfx, line, y);

        gfx.setFont(defaultFont);
        gfx.setColor(defaultColor);
        y += fm.getHeight();
        x = this.paintSyntaxLine(currentLine, currentLineTokens, styles, this,
                gfx, x, y);

    }

    protected void paintHighlight(Graphics gfx, int line, int y) {
        if (line >= textArea.getSelectionStartLine()
                && line <= textArea.getSelectionEndLine())
            paintLineHighlight(gfx, line, y);

        if (highlights != null)
            highlights.paintHighlight(gfx, line, y);

        if (bracketHighlight && line == textArea.getBracketLine())
            paintBracketHighlight(gfx, line, y);

        if (line == textArea.getCaretLine())
            paintCaret(gfx, line, y);
    }

    protected void paintLineHighlight(Graphics gfx, int line, int y) {
        int height = fm.getHeight();
        y += fm.getLeading() + fm.getMaxDescent();

        int selectionStart = textArea.getSelectionStart();
        int selectionEnd = textArea.getSelectionEnd();

        if (selectionStart == selectionEnd) {
            if (lineHighlight) {
                gfx.setColor(lineHighlightColor);
                gfx.fillRect(0, y, getWidth(), height);
            }
        } else {
            gfx.setColor(selectionColor);

            int selectionStartLine = textArea.getSelectionStartLine();
            int selectionEndLine = textArea.getSelectionEndLine();
            int lineStart = textArea.getLineStartOffset(line);

            int x1, x2;
            if (textArea.isSelectionRectangular()) {
                int lineLen = textArea.getLineLength(line);
                x1 = textArea._offsetToX(line, Math.min(lineLen, selectionStart
                        - textArea.getLineStartOffset(selectionStartLine)));
                x2 = textArea._offsetToX(line, Math.min(lineLen, selectionEnd
                        - textArea.getLineStartOffset(selectionEndLine)));
                if (x1 == x2)
                    x2++;
            } else if (selectionStartLine == selectionEndLine) {
                x1 = textArea._offsetToX(line, selectionStart - lineStart);
                x2 = textArea._offsetToX(line, selectionEnd - lineStart);
            } else if (line == selectionStartLine) {
                x1 = textArea._offsetToX(line, selectionStart - lineStart);
                x2 = getWidth();
            } else if (line == selectionEndLine) {
                x1 = 0;
                x2 = textArea._offsetToX(line, selectionEnd - lineStart);
            } else {
                x1 = 0;
                x2 = getWidth();
            }

            // "inlined" min/max()
            gfx.fillRect(x1 > x2 ? x2 : x1, y, x1 > x2 ? (x1 - x2) : (x2 - x1),
                    height);
        }

    }

    protected void paintBracketHighlight(Graphics gfx, int line, int y) {
        int position = textArea.getBracketPosition();
        if (position == -1)
            return;
        y += fm.getLeading() + fm.getMaxDescent();
        int x = textArea._offsetToX(line, position);
        gfx.setColor(bracketHighlightColor);
        // Hack!!! Since there is no fast way to get the character
        // from the bracket matching routine, we use ( since all
        // brackets probably have the same width anyway
        if (textArea.pcffont != null) {
            gfx.drawRect(x, y, textArea.pcffont.charWidth('c') - 1,
                    textArea.pcffont.getHeight());
            return;
        }
        gfx.drawRect(x, y, fm.charWidth('(') - 1, fm.getHeight() - 1);
    }

    protected void paintCaret(Graphics gfx, int line, int y) {
        if (textArea.isCaretVisible()) {
            if (textArea.pcffont == null) {
                int offset = textArea.getCaretPosition()
                        - textArea.getLineStartOffset(line);
                int caretX = textArea._offsetToX(line, offset);
                int caretWidth = ((blockCaret || textArea.isOverwriteEnabled()) ? fm
                        .charWidth('w')
                        : 1);
                y += fm.getLeading() + fm.getMaxDescent();
                int height = fm.getHeight();

                gfx.setColor(caretColor);

                if (textArea.isOverwriteEnabled()) {
                    gfx.fillRect(caretX, y + height - 1, caretWidth, 1);
                } else {
                    gfx.drawRect(caretX, y, caretWidth - 1, height - 1);
                }
            } else {
                int offset = textArea.getCaretPosition()
                        - textArea.getLineStartOffset(line);
                int caretX = textArea._offsetToX(line, offset);
                int caretWidth = ((blockCaret || textArea.isOverwriteEnabled()) ? textArea.pcffont
                        .charWidth('w')
                        : 1);
                y += fm.getLeading() + fm.getMaxDescent();
                int height = fm.getHeight();

                gfx.setColor(caretColor);

                if (textArea.isOverwriteEnabled()) {
                    gfx.fillRect(caretX, y + height - 1, caretWidth, 1);
                } else {
                    gfx.drawRect(caretX, y, caretWidth - 1, height - 1);
                }
            }
        }
    }

    public int drawTabbedPCFText(PFont pcf, Segment s, int x, int y,
            Graphics g, TabExpander e) {

        if (pcf == null)
            return 0;

        int nextX = x;
        char[] txt = s.array;
        int txtOffset = s.offset;
        int flushLen = 0;
        int flushIndex = s.offset;
        int spaceAddon = 0;
        int spaceAddonLeftoverEnd = -1;
        int startJustifiableContent = 0;
        int endJustifiableContent = 0;
        int startOffset = 0;

        int n = s.offset + s.count;
        for (int i = txtOffset; i < n; i++) {
            if (txt[i] == '\t'
                    || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
                            && (txt[i] == ' ') && startJustifiableContent <= i && i <= endJustifiableContent)) {
                if (flushLen > 0) {
                    nextX = pcf.drawString(g, txt, flushIndex, flushLen, x, y);
                    // SwingUtilities2.drawChars(component, g, txt,
                    // flushIndex, flushLen, x, y);
                    flushLen = 0;
                }
                flushIndex = i + 1;
                if (txt[i] == '\t') {
                    if (e != null) {
                        nextX = (int) e.nextTabStop((float) nextX, startOffset
                                + i - txtOffset);
                    } else {
                        nextX += pcf.charWidth('w');
                    }
                } else if (txt[i] == ' ') {
                    nextX += pcf.charWidth(' ') + spaceAddon;
                    if (i <= spaceAddonLeftoverEnd) {
                        nextX++;
                    }
                }
                x = nextX;
            } else if ((txt[i] == '\n') || (txt[i] == '\r')) {
                if (flushLen > 0) {
                    nextX = pcf.drawString(g, txt, flushIndex, flushLen, x, y);
                    // SwingUtilities2.drawChars(component, g, txt,
                    // flushIndex, flushLen, x, y);
                    flushLen = 0;
                }
                flushIndex = i + 1;
                x = nextX;
            } else {
                flushLen += 1;
            }
        }
        if (flushLen > 0) {
            nextX = pcf.drawString(g, txt, flushIndex, flushLen, x, y);
            // nextX = SwingUtilities2.drawChars(component, g, txt, flushIndex,
            // flushLen, x, y);
        }
        return nextX;
    }

    public int getTabbedPCFTextWidth(Segment s, int x, TabExpander e) {
        int nextX = x;
        char[] txt = s.array;
        int txtOffset = s.offset;
        int n = s.offset + s.count;
        int charCount = 0;
        int spaceAddon = 0;
        int spaceAddonLeftoverEnd = -1;
        int startJustifiableContent = 0;
        int endJustifiableContent = 0;
        int startOffset = 0;

        for (int i = txtOffset; i < n; i++) {
            if (txt[i] == '\t'
                    || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
                            && (txt[i] == ' ') && startJustifiableContent <= i && i <= endJustifiableContent)) {
                nextX += pcfCharsWidth(txt, i - charCount, charCount);
                charCount = 0;
                if (txt[i] == '\t') {
                    if (e != null) {
                        nextX = (int) e.nextTabStop((float) nextX, startOffset
                                + i - txtOffset);
                    } else {
                        nextX += textArea.pcffont.charWidth(' ');
                    }
                } else if (txt[i] == ' ') {
                    nextX += textArea.pcffont.charWidth(' ') + spaceAddon;
                    if (i <= spaceAddonLeftoverEnd) {
                        nextX++;
                    }
                }
            } else if (txt[i] == '\n') {
                // Ignore newlines, they take up space and we shouldn't be
                // counting them.
                nextX += pcfCharsWidth(txt, i - charCount, charCount);
                charCount = 0;
            } else {
                charCount++;
            }
        }
        nextX += pcfCharsWidth(txt, n - charCount, charCount);
        return nextX - x;
    }

    public int paintSyntaxLine(Segment line, Token tokens,
            SyntaxStyle[] styles, TabExpander expander, Graphics gfx, int x,
            int y) {
        Font defaultFont = gfx.getFont();
        Color defaultColor = gfx.getColor();

        int offset = 0;
        for (;;) {
            byte id = tokens.id;
            if (id == Token.END)
                break;

            PFont pcf = null;

            int length = tokens.length;
            if (id == Token.NULL) {
                if (!defaultColor.equals(gfx.getColor()))
                    gfx.setColor(defaultColor);
                if (!defaultFont.equals(gfx.getFont()))
                    gfx.setFont(defaultFont);
                if (textArea.pcffont != null) {
                    textArea.pcffont.setFgColor(defaultColor);
                }

                pcf = textArea.pcffont;
            } else {
                if (textArea.pcffont != null) {
                    textArea.pcffont.setFgColor(styles[id].color);
                    // pcf = styles[id].bold ? textArea.pcfbfont :
                    // textArea.pcffont;
                }

                styles[id].setGraphicsFlags(gfx, defaultFont);
            }

            line.count = length;
            if (textArea.pcffont != null) {

                x = drawTabbedPCFText(pcf, line, x, y, gfx, expander);
            } else {
                x = Utilities.drawTabbedText(line, x, y, gfx, this, 0);
            }

            line.offset += length;
            offset += length;

            tokens = tokens.next;
        }

        return x;
    }

    public int pcfCharsWidth(char data[], int off, int len) {
        return stringWidth(new String(data, off, len));
    }

    public int stringWidth(String str) {
        int len = str.length();
        // char data[] = new char[len];
        // str.getChars(0, len, data, 0);
        return textArea.pcffont.charWidth('w') * len;
    }

}
